
#include <BFont>
#include <BStyle>
#include <BOpenGL>
#include <BPainter>
#include <BPixmap>
#include "member_BWidget.h"
#include "member_BStyle.h"
#include "ITipsBoard.h"
#include "IGlobal.h"

using namespace BWE;

ITipsBoard::ITipsBoard()
{
	root = 0;
	widget = 0;
	dirty = false;
	visible = false;
	resized = true;
	fbo = 0;
	tbo = 0;
	tipsTick = 0;
	border = 0;
}
ITipsBoard::~ITipsBoard()
{

}

void ITipsBoard::reset(BWidget* hoveredWidget)
{
	if (hoveredWidget && hoveredWidget->realTips().empty())
		hoveredWidget = 0;
	if (widget != hoveredWidget)
	{
		widget = hoveredWidget;
		styles.reset();
		border = 0;
		margin = 0;
		if (widget)
		{
			tipsTick = bTime();
			root = widget->root();
		}
		else
		{
			tipsTick = 0;
			root = 0;
		}
		dirty = true;
		visible = false;
	}
}
void ITipsBoard::freshStyles()
{
	BWidget* parent = widget;
	while (parent)
	{
		if (const BStyle* realStyle = parent->realStyle())
		{
			if (const BStyle* tipsStyle = realStyle->annex("tips"))
			{
				styles.append(tipsStyle);
			}
		}
		parent = parent->parent();
	}
	if (const BStyle* tipsStyle = app_style->annex("tips"))
	{
		styles.append(tipsStyle);
	}

	const BValue& borderValue = findValue(Value_Border);
	if (borderValue.valid())
		border = borderValue;
	const BValue& leftValue = findValue(Value_Margin_Left);
	if (leftValue.valid())
		margin.left() = leftValue;
	const BValue& topValue = findValue(Value_Margin_Top);
	if (topValue.valid())
		margin.top() = topValue;
	const BValue& rightValue = findValue(Value_Margin_Right);
	if (rightValue.valid())
		margin.right() = rightValue;
	const BValue& bottomValue = findValue(Value_Margin_Bottom);
	if (bottomValue.valid())
		margin.bottom() = bottomValue;
}
const BValue& ITipsBoard::findValue(StyleValue name)
{
	for (int i = 0; i < styles.size(); i++)
	{
		const BStyle* style = styles[i];
		const BValue& value = style_member(style)->valueMap(name);
		if (value.valid())
			return value;
	}
	return app_style->value(name);
}
const BColor* ITipsBoard::findColor(StyleColor name)
{
	for (int i = 0; i < styles.size(); i++)
	{
		const BStyle* style = styles[i];
		auto it = style_member(style)->colorMap.find(name);
		if (it.valid())
			return &(*it);
	}
	auto it = style_member(app_style)->colorMap.find(name);
	if (it.valid())
		return &(*it);
	return 0;
}
const BImage* ITipsBoard::findImage(StyleImage name)
{
	for (int i = 0; i < styles.size(); i++)
	{
		const BStyle* style = styles[i];
		auto it = style_member(style)->imageMap.find(name);
		if (it.valid())
			return *it;
	}
	auto it = style_member(app_style)->imageMap.find(name);
	if (it.valid())
		return *it;
	return 0;
}

bool ITipsBoard::update()
{
	if (widget && tipsTick > 0 && !visible)
	{
		BReal now = bTime();
		if (now - tipsTick > app_tipsDelay)
		{
			visible = true;
			return true;
		}
	}
	return false;
}
void ITipsBoard::fresh()
{
	if (!widget)
		return;

	const BString& tips = widget->realTips();
	if (tips.empty())
		return;

	if (styles.empty())
		freshStyles();

	block.text.reset();
	for (int i = 0; i < styles.size(); i++)
	{
		if (styles[i]->translate(tips, block.text))
			break;
	}
	if (block.text.empty())
	{
		block.text = tips;
	}

	int pageWidth = 0;
	const BValue& widthValue = findValue(Value_Width);
	if (widthValue.valid())
		pageWidth = widthValue;
	if (pageWidth > root->width())
		pageWidth = root->width();

	const BValue& fontValue = findValue(Value_Font);
	block.align = findValue(Value_Align);
	if (fontValue.valid())
		block.font = (BFont*)fontValue;
	else
		block.font = widget->realFont();

	const BValue& spacingValue = findValue(Value_Spacing);
	if (spacingValue.valid())
		block.lineSpacing = spacingValue;
	else
		block.lineSpacing = 0;

	block.dirty = true;
	block.reset();
	block.adjust(pageWidth);

	BPoint offset(block.font->size());
	const BValue& offsetValue = findValue(Value_Offset);
	if (offsetValue.valid())
		offset = offsetValue;

	if (block.width() < pageWidth)
		block.setWidth(pageWidth);

	area.set(pos + offset, block.size());
	area.top() -= border + margin.top();
	area.left() -= border + margin.left();
	area.right() += border + margin.right();
	area.bottom() += border + margin.bottom();
	block.setPos(border + margin.left(), border + margin.top());

	crect = area.size();
	crect.left() += border;
	crect.right() -= border;
	crect.top() += border;
	crect.bottom() -= border;

	int xoffset = 0;
	int yoffset = 0;
	BRect clip = root->centerRect();
	if (root->pixmap())
		clip = root->pixmap()->size();
	if (area.left() < clip.left())
		xoffset += clip.left() - area.left();
	if (area.top() < clip.top())
		yoffset += clip.top() - area.top();
	if (area.right() > clip.right())
		xoffset += clip.right() - area.right();
	if (area.bottom() > clip.bottom())
		yoffset += clip.bottom() - area.bottom();
	area.move(xoffset, yoffset);
	resized = true;

	BSize radiusLeftTop = findValue(Value_Radius_Left_Top);
	BSize radiusRightTop = findValue(Value_Radius_Right_Top);
	BSize radiusLeftBottom = findValue(Value_Radius_Left_Bottom);
	BSize radiusRightBottom = findValue(Value_Radius_Right_Bottom);
	if (radiusLeftTop.empty() && radiusRightTop.empty() && radiusLeftBottom.empty() && radiusRightBottom.empty())
	{
		round = 0;
	}
	else
	{
		if (round.empty())
			round = new IRound();
		round->radiusLeftTop = radiusLeftTop;
		round->radiusRightTop = radiusRightTop;
		round->radiusLeftBottom = radiusLeftBottom;
		round->radiusRightBottom = radiusRightBottom;
	}

	if (round)
	{
		int width = area.width();
		int height = area.height();
		round->frameSize.set((float)width, (float)height);
		round->centerLeftTop.set(round->radiusLeftTop.width(), round->radiusLeftTop.height());
		round->centerRightTop.set(width - round->radiusRightTop.width(), round->radiusRightTop.height());
		round->centerLeftBottom.set(round->radiusLeftBottom.width(), height - round->radiusLeftBottom.height());
		round->centerRightBottom.set(width - round->radiusRightBottom.width(), height - round->radiusRightBottom.height());

		BPointfArray outerPoints;
		BPointfArray innerPoints;

		BRectf outerRect(0, 0, width - 1, height - 1);
		round->createPoints(outerRect, outerPoints);

		BRectf innerRect = outerRect;
		innerRect.left() += border;
		innerRect.right() -= border;
		innerRect.top() += border;
		innerRect.bottom() -= border;

		round->createPoints(innerRect, innerPoints);
		round->createTriangles(outerPoints, innerPoints, round->borderPoints);

		round->backgroundPoints = innerPoints;
		round->backgroundPoints.prepend(block.center());

		outerPoints.reset();
		round->createLeftTopPoints(innerRect, outerPoints);
		round->createRightTopPoints(innerRect, outerPoints);

		outerPoints = innerPoints;
		round->createPoints(block, innerPoints);
		round->createTriangles(outerPoints, innerPoints, round->marginPoints);

		round->centerPoints = innerPoints;
		round->centerPoints.prepend(block.center());
	}
}
void ITipsBoard::paint()
{
	BPainter painter(area.size());

	if (const BImage* image = findImage(Image_Background))
	{
		painter.drawImage(crect, image, margin);
	}
	else if (const BColor* background = findColor(Color_Background))
	{
		painter.setColor(*background);
		if (round)
		{
			glEnable(GL_MULTISAMPLE);
			glBegin(GL_TRIANGLE_FAN);
			for (int i = 0; i < round->backgroundPoints.size(); i++)
			{
				const BPointf& p = round->backgroundPoints[i];
				glVertex2f(p.x(), p.y());
			}
			glEnd();
			glDisable(GL_MULTISAMPLE);
		}
		else
		{
			painter.fillRect(0, area.size());
		}
	}

	if (margin.valid())
	{
		if (const BImage* image = findImage(Image_Margin))
		{
			painter.bind(image);
			painter.fillRect(crect, margin);
			painter.unbind();
		}
		else if (const BColor* color = findColor(Color_Margin))
		{
			painter.setColor(*color);
			if (round)
			{
				glEnable(GL_MULTISAMPLE);
				glBegin(GL_TRIANGLES);
				for (int i = 0; i < round->marginPoints.size(); i++)
				{
					const BPointf& p = round->marginPoints[i];
					glVertex2f(p.x(), p.y());
				}
				glEnd();
				glDisable(GL_MULTISAMPLE);
			}
			else
			{
				BRect rect(border, border, area.width() - border - border, area.height() - border - border);
				BRect crect = block;
				int left = margin.left();
				int top = margin.top();
				int right = margin.right();
				int bottom = margin.bottom();
				painter.fillRect(rect.left(), crect.top(), left, crect.height());
				painter.fillRect(crect.left(), rect.top(), crect.width(), top);
				painter.fillRect(crect.right(), crect.top(), right, crect.height());
				painter.fillRect(crect.left(), crect.bottom(), crect.width(), bottom);
				painter.fillRect(rect.left(), rect.top(), left, top);
				painter.fillRect(crect.right(), rect.top(), right, top);
				painter.fillRect(rect.left(), crect.bottom(), left, bottom);
				painter.fillRect(crect.right(), crect.bottom(), right, bottom);
			}
		}
	}

	if (border > 0)
	{
		if (const BImage* image = findImage(Image_Border))
		{
			painter.bind(image);
			BRect rect(0, 0, area.width(), area.height());
			painter.fillRect(rect, BMargin(border, border, border, border));
			painter.unbind();
		}
		else if (const BColor* color = findColor(Color_Border))
		{
			painter.setColor(*color);
			if (round)
			{
				glEnable(GL_MULTISAMPLE);
				glBegin(GL_TRIANGLES);
				for (int i = 0; i < round->borderPoints.size(); i++)
				{
					const BPointf& p = round->borderPoints[i];
					glVertex2f(p.x(), p.y());
				}
				glEnd();
				glDisable(GL_MULTISAMPLE);
			}
			else
			{
				int width = area.width();
				int height = area.height();
				painter.fillRect(0, border, border, height - border - border);
				painter.fillRect(0, 0, width, border);
				painter.fillRect(width - border, border, border, height - border - border);
				painter.fillRect(0, height - border, width, border);
			}
		}
	}

	const BColor* textColor = findColor(Color_Text);
	if (textColor)
	{
		painter.setColor(*textColor);
		block.paint(painter);
	}
}
void ITipsBoard::frame(GLuint msfbo, GLenum mode)
{
	int width = area.width();
	int height = area.height();
	if (fbo == 0 || tbo == 0)
		resized = true;
	if (fbo_size != area.size())
		resized = true;
	if (resized)
	{
		GLint backup_frame_buffer;
		glGetIntegerv(GL_FRAMEBUFFER_BINDING, &backup_frame_buffer);
		if (!fbo)
		{
			glGenFramebuffers(1, &fbo);
		}
		if (fbo)
		{
			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
		}
		if (!tbo)
		{
			glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
			glGenTextures(1, &tbo);
		}
		if (tbo)
		{
			glBindTexture(GL_TEXTURE_2D, tbo);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
			glBindTexture(GL_TEXTURE_2D, 0);
			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tbo, 0);
		}
		glBindFramebuffer(GL_FRAMEBUFFER, backup_frame_buffer);
		fbo_size = area.size();
		resized = false;
	}
	if (fbo)
	{
		GLint backup_frame_buffer;
		glGetIntegerv(GL_FRAMEBUFFER_BINDING, &backup_frame_buffer);

		glBindFramebuffer(GL_FRAMEBUFFER, fbo);
		glDrawBuffer(GL_COLOR_ATTACHMENT0);
		glReadBuffer(GL_COLOR_ATTACHMENT0);

		if (msfbo > 0)
		{
			glBindFramebuffer(GL_FRAMEBUFFER, msfbo);
			glDrawBuffer(mode);
			glReadBuffer(mode);
		}

		glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
		glClear(GL_COLOR_BUFFER_BIT);

		glDisable(GL_LIGHTING);
		glDisable(GL_DEPTH_TEST);
		glDisable(GL_CULL_FACE);
		glDisable(GL_MULTISAMPLE);

		glEnable(GL_BLEND);
		glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE);
		glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD);
		glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE);

		glViewport(0, 0, width, height);

		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		glOrtho(0, width, height, 0, -1, 1);

		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();

		paint();

		if (msfbo > 0)
		{
			glBindFramebuffer(GL_READ_FRAMEBUFFER, msfbo);
			glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
			glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
		}

		glBindFramebuffer(GL_FRAMEBUFFER, backup_frame_buffer);
	}
}

